/**
 * Copyright (C) 2001-2017 by RapidMiner and the contributors
 * 
 * Complete list of developers available at our web site:
 * 
 * http://rapidminer.com
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Affero General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License along with this program.
 * If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.tools.syntax;

/*
 * KeywordMap.java - Fast keyword->id map Copyright (C) 1998, 1999 Slava Pestov Copyright (C) 1999
 * Mike Dillon
 * 
 * You may use and modify this package for any purpose. Redistribution is permitted, in both source
 * and binary form, provided that this notice remains intact in all source distributions of this
 * package.
 */

import javax.swing.text.Segment;


/**
 * A <code>KeywordMap</code> is similar to a hashtable in that it maps keys to values. However, the
 * `keys' are Swing segments. This allows lookups of text substrings without the overhead of
 * creating a new string object.
 * <p>
 * This class is used by <code>CTokenMarker</code> to map keywords to ids.
 * 
 * @author Slava Pestov, Mike Dillon, Ingo Mierswa
 */
public class KeywordMap {

	/**
	 * Creates a new <code>KeywordMap</code>.
	 * 
	 * @param ignoreCase
	 *            True if keys are case insensitive
	 */
	public KeywordMap(boolean ignoreCase) {
		this(ignoreCase, 52);
		this.ignoreCase = ignoreCase;
	}

	/**
	 * Creates a new <code>KeywordMap</code>.
	 * 
	 * @param ignoreCase
	 *            True if the keys are case insensitive
	 * @param mapLength
	 *            The number of `buckets' to create. A value of 52 will give good performance for
	 *            most maps.
	 */
	public KeywordMap(boolean ignoreCase, int mapLength) {
		this.mapLength = mapLength;
		this.ignoreCase = ignoreCase;
		map = new Keyword[mapLength];
	}

	/**
	 * Looks up a key.
	 * 
	 * @param text
	 *            The text segment
	 * @param offset
	 *            The offset of the substring within the text segment
	 * @param length
	 *            The length of the substring
	 */
	public byte lookup(Segment text, int offset, int length) {
		if (length == 0) {
			return Token.NULL;
		}
		Keyword k = map[getSegmentMapKey(text, offset, length)];
		while (k != null) {
			if (length != k.keyword.length) {
				k = k.next;
				continue;
			}
			if (SyntaxUtilities.regionMatches(ignoreCase, text, offset, k.keyword)) {
				return k.id;
			}
			k = k.next;
		}
		return Token.NULL;
	}

	/**
	 * Adds a key-value mapping.
	 * 
	 * @param keyword
	 *            The key
	 * @param id
	 *            The value
	 */
	public void add(String keyword, byte id) {
		int key = getStringMapKey(keyword);
		map[key] = new Keyword(keyword.toCharArray(), id, map[key]);
	}

	/**
	 * Returns true if the keyword map is set to be case insensitive, false otherwise.
	 */
	public boolean getIgnoreCase() {
		return ignoreCase;
	}

	/**
	 * Sets if the keyword map should be case insensitive.
	 * 
	 * @param ignoreCase
	 *            True if the keyword map should be case insensitive, false otherwise
	 */
	public void setIgnoreCase(boolean ignoreCase) {
		this.ignoreCase = ignoreCase;
	}

	// protected members
	protected int mapLength;

	protected int getStringMapKey(String s) {
		return (Character.toUpperCase(s.charAt(0)) + Character.toUpperCase(s.charAt(s.length() - 1))) % mapLength;
	}

	protected int getSegmentMapKey(Segment s, int off, int len) {
		return (Character.toUpperCase(s.array[off]) + Character.toUpperCase(s.array[off + len - 1])) % mapLength;
	}

	// private members
	static class Keyword {

		public Keyword(char[] keyword, byte id, Keyword next) {
			this.keyword = keyword;
			this.id = id;
			this.next = next;
		}

		public char[] keyword;

		public byte id;

		public Keyword next;
	}

	private Keyword[] map;

	private boolean ignoreCase;
}
